//=============================================================================
// SentinelController - Controls Sentinels.
//=============================================================================
class SentinelController extends BaseSentinelController;

var() float TargetWaitTime;
var() int MaxFireFails;
var int FailedFireCounter;

var vector PredictedTargetLocation;

var bool bForceTarget; //Set to true to force the Sentinel to target an actor that is not its enemy.

var rotator Aim;

function DisplayDebug(Canvas Canvas, out float YL, out float YPos)
{
	Focus = Enemy;

	Super.DisplayDebug(Canvas,YL, YPos);

	Canvas.SetDrawColor(255,255,255);

	Canvas.DrawText("Aim.Yaw:"@normalize(Aim).Yaw@"Aim.Pitch:"@normalize(Aim).Pitch);
	YPos += YL;
	Canvas.SetPos(4,YPos);
}

function InitPlayerReplicationInfo(){}

function Possess(Pawn aPawn)
{
	Super.Possess(aPawn);

	if(aPawn == None)
		Destroy();

	if(!aPawn.IsA('Sentinel'))
	{
		warn("Tried to possess something that was not a Sentinel.");
		aPawn.UnPossessed();
		Destroy();
		return;
	}

	sent = Sentinel(aPawn);
	FovAngle = Sentinel(sent).SensorAngle;

	PlayerReplicationInfo = Spawn(class'SentinelPlayerReplicationInfo', Self,,vect(0,0,0),rot(0,0,0));

	if(sent.InstigatorController.PlayerReplicationInfo.Team != None)
		PlayerReplicationInfo.Team = sent.InstigatorController.PlayerReplicationInfo.Team;

	PlayerReplicationInfo.SetPlayerName("");
	PlayerReplicationInfo.bWelcomed = true; //Possibly fixes multiple entry messages?
	sent.PlayerReplicationInfo = PlayerReplicationInfo;
}

function rotator GetViewRotation()
{
	return Sentinel(sent).CurrentAim;
}



function ChangedWeapon(){}

//Decide whether the detected pawn can be shot at or not.
function bool PossiblyTarget(Pawn PotentialTarget)
{
	if(PotentialTarget == None || PotentialTarget.Health <= 0)
		return false;

	if(PotentialTarget == sent)
		return false;

	if(PotentialTarget.IsA('BaseSentinel') && BaseSentinel(PotentialTarget).myteam == sent.myteam)
		return false;

	if(SameTeamAs(PotentialTarget.Controller))
		return false;

	if(!LOSTo(PotentialTarget))
        return false;
	//Invisible pawns may be able to sneak past.
	if(PotentialTarget.Visibility < 128 && FRand() < 0.1)
		return false;

	if(PotentialTarget == Enemy)
		return true;

	if(Enemy != none && Enemy.Health > 0 && (bEnemyIsVisible || Level.TimeSeconds - LastSeenTime < 1))
		return false;

	return true;
}

function SetEnemyInfo(bool bCanSee, optional bool bInitial)
{
	Sentinel(sent).NewTarget(Enemy);

	if(bCanSee || bInitial)
	{
		LastSeenTime = Level.TimeSeconds;
		LastSeenPos = Enemy.Location;
	}

	bEnemyIsVisible = bCanSee;
	bForceTarget = false;
}

//Waiting for something to shoot at.
state Waiting
{
	function BeginState()
	{
		Enemy = None;
		SetTimer(0.0, false);
		Sentinel(sent).SetWaiting();
		Sentinel(sent).PlaySound(Sentinel(sent).ActivateSound);
	}

	event HearNoise(float Loudness, Actor NoiseMaker)
	{
		if(FRand() < Loudness + Sentinel(sent).HearingAdjustment && NoiseMaker.IsA('Pawn'))
		{
			if(PossiblyTarget(Pawn(NoiseMaker)))
			{
				Enemy = Pawn(NoiseMaker);
				SetEnemyInfo(false, true);
				GotoState('Tracking');
			}
		}
	}

	event SeePlayer(Pawn Seen)
	{
		if(PossiblyTarget(Seen))
		{
			Enemy = Seen;
			SetEnemyInfo(true);
			GotoState('Tracking');
		}
	}

	event SeeMonster(Pawn Seen)
	{
		if(PossiblyTarget(Seen))
		{
			Enemy = Seen;
			SetEnemyInfo(true);
			GotoState('Tracking');
		}
	}

	//Automatically attack enemy pawns who cause damage.
	function NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class<DamageType> DamageType, vector Momentum)
	{
		if(InstigatedBy != None && DamageType != class'DamTypeSentinel')
		{
			if(PossiblyTarget(InstigatedBy))
			{
				Enemy = InstigatedBy;
				SetEnemyInfo(false, true);
				GotoState('Tracking');
			}
		}
	}
}

//Trying to shoot a target.
state Tracking
{
	function BeginState()
	{
		Sentinel(sent).PlaySound(Sentinel(sent).AquiredSound);
		SetTimer(Sentinel(sent).SWeapon.FireRate, true);
	}

	function ChangedWeapon()
	{
		SetTimer(Sentinel(sent).SWeapon.FireRate, true);
	}

	//Check for a better target.
	event SeePlayer(Pawn Seen)
	{
		if(bForceTarget)
			return;
		else if(PossiblyTarget(Seen))
		{
			Enemy = Seen;
			SetEnemyInfo(true);
		}
		else if(Seen == Enemy)
			SetEnemyInfo(true);
	}

	//Check for a better target.
	event SeeMonster(Pawn Seen)
	{
		if(bForceTarget)
			return;
		else if(PossiblyTarget(Seen))
		{
			Enemy = Seen;
			SetEnemyInfo(true);
		}
		else if(Seen == Enemy)
			SetEnemyInfo(true);
	}

	//Keep targeting hidden players for a while, unless a better target comes in to view.
	event EnemyNotVisible()
	{
		if(Level.TimeSeconds - LastSeenTime > TargetWaitTime)
			GotoState('Waiting');
		else
			SetEnemyInfo(false);
	}

	//Target inflicter of damage if possible.
	function NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class<DamageType> damageType, vector Momentum)
	{
		if(InstigatedBy != None && !InstigatedBy.IsA('Sentinel'))
		{
			if(PossiblyTarget(InstigatedBy))
			{
				Enemy = InstigatedBy;
				SetEnemyInfo(false);
				GotoState('Tracking');
			}
		}
	}

	function Timer()
	{
		if(bEnemyIsVisible && !bForceTarget)
		{
			if(PossiblyTarget(Enemy))
			{
				if(Sentinel(sent).FireAt(PredictedTargetLocation))
					FailedFireCounter = 0;
				else
					FailedFireCounter++;
			}
			else
				GotoState('Waiting');
		}

		//Target is avoiding the line of fire too well?
		if(FailedFireCounter > MaxFireFails)
			SetEnemyInfo(false);
	}

	//Aiming.
	function Tick(float dt)
	{
		local float TempPitch;
		local float PredictionTime;
		local vector PredictionVelocity;
		local vector Origin;

		if(Enemy == None)
		{
			GotoState('Waiting');
			return;
		}
		else if(!bForceTarget || Target == None)
			Target = Enemy;

		Origin = sent.Location;
		Origin.Z += sent.BaseEyeHeight;

		//If enemy is visible, predict where to shoot, otherwise simply point at where they were last seen.
		//TODO: Sentinel should be aware of projectile's acceleration (and maxspeed) for prediction.
		//TODO: Take into account target's acceleration.
		//TODO: Iterative prediction (use PredictedTargetLocation to calculate PredictionTime).
		//TODO: Guess where falling targets will land and shoot there (trace along predicted path).
		if(bEnemyIsVisible)
		{
			//How long it will take for projectile to reach target.
			if(Sentinel(sent).SWeapon.ProjectileClass != None)
			{
				PredictionTime = VSize(Target.Location - Origin) / Sentinel(sent).SWeapon.ProjectileClass.Default.Speed;

				//Where the target will probably be by then.
				if(VSize(Enemy.Velocity) > 500)
					PredictionVelocity = normal(Target.Velocity) * 500;
				else
					PredictionVelocity = Target.Velocity;

				PredictedTargetLocation = Target.Location + (PredictionVelocity * PredictionTime * Sentinel(sent).AimAhead);
			}
			else
				PredictedTargetLocation = Target.Location;
		}
		else
			PredictedTargetLocation = LastSeenPos;

		Aim = rotator(PredictedTargetLocation - Origin);

		TempPitch = Acos(normal(PredictedTargetLocation - Origin) dot vect(0, 0, -1));
		TempPitch *= 32768 / 3.14159265359;
		TempPitch -= 16384;
		Aim.Pitch = TempPitch;

		Aim = normalize(Aim);
		Sentinel(sent).DesiredPitch = Aim.Pitch;
		Sentinel(sent).DesiredYaw = Aim.Yaw;
	}
}

defaultproperties
{
     TargetWaitTime=5.000000
     MaxFireFails=3
     FovAngle=270.000000
     bIsPlayer=True
     bCanOpenDoors=False
     bCanDoSpecial=False
     bAdrenalineEnabled=False
     PlayerReplicationInfoClass=None
     PawnClass=Class'taskforces.Sentinel'
}
